<?php

CCore::Using('base.utils');

class CWebMailServer
{
	/**
	 * @var CBaseInputData
	 */
	private $_oInputData;

	/**
	 * @var CWebMail
	 */
	private $_oWebMail;

	/**
	 * @param CBaseInputData $oInpitData
	 *  @return CWebMailServer
	 */
	public function __construct(CBaseInputData $oInpitData)
	{
		$this->_oWebMail = null;
		$this->_oInputData = $oInpitData;
	}

	/**
	 * @return CWebMailServer
	 */
	public function Run()
	{
		@header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
		@header('Content-Type: text/javascript; charset=UTF-8');
		@header('Pragma: no-cache');

		$sError = '';
		$sFunction = 'Do'.$this->_oInputData->GetPostValue('action', '');
		if (is_callable(array(&$this, $sFunction)))
		{
			if (!in_array($sFunction, array('DoLogin', 'DoGetApplicationSettings')))
			{
				$oAccount = CAccount::CreateAccountFromHash($this->_oInputData->GetCookieValue('xwm_auth', ''));
				if ($oAccount)
				{
					$this->_oWebMail = new CCoreLabsWebMail($oAccount);
				}
				else
				{
					$sError = 'Authentication error';
				}
			}

			if (empty($sError))
			{
				try
				{
					call_user_func(array(&$this, $sFunction));
				}
				catch (CCoreWebMailException $oException)
				{
					$sError = $oException->getMessage();
				}
			}
		}
		else
		{
			$sError = 'Internal error';
		}

		if (!empty($sError))
		{
			echo '{ "Action": '.CCoreBaseUtils::Php2js($sFunction).', "Response": false, "Error": '.CCoreBaseUtils::Php2js($sError).' }';
		}

		return $this;
	}

	/**
	 * @return CWebMailServer
	 */
	public function End()
	{
		if ($this->_oWebMail)
		{
			$this->_oWebMail->ForceCloseConnection();
		}

		return $this;
	}

	/**
	 * @return void
	 */
	public function DoGetBase()
	{
		echo '[ ';
		$this->DoGetAccountSettings(true);
		echo ', ';
		$this->DoGetFolders(true);
		echo ', ';
		if (0 < count($this->_oInputData->GetPostValue('uids', array())))
		{
			$this->DoGetMessages(true);
		}
		else
		{
			$this->DoGetMessagesHeaders(true);
		}
		echo ' ]';
	}

	/**
	 * @return void
	 */
	public function DoLogin()
	{
		$sResponse = false;
		$aParams = null;
		
		$oAccount = new CAccount(
			C_WEBMAIL_INC_MAIL_PROTOCOL_IMAP,
			$this->_oInputData->GetPostValue('sIncHost', ''),
			$this->_oInputData->GetPostValue('iIntPort', -1),
			$this->_oInputData->GetPostValue('sIncLogin', ''),
			$this->_oInputData->GetPostValue('sIncPassword', ''));

		$oWebMail = new CCoreLabsWebMail($oAccount);
		if ($oWebMail->TestIncConnection())
		{
			$aParams = array('Auth' => $oAccount->GetEncodedHash());
			$sResponse = true;
		}

		$this->_echoActionAndResponse('Login', $sResponse, $aParams);
	}

	/**
	 * @return void
	 */
	public function DoGetApplicationSettings()
	{
		$aResponse = array(
			'Title' => 'MyWebMail',
			'ExpiresSession' => 1,
			'ExpiresSignMe' => 30,
			'CheckField' => true
		);
		
		$this->_echoActionAndResponse('GetApplicationSettings', $aResponse);
	}

	/**
	 * @param bool $bIsBase = false
	 * @return void
	 */
	public function DoGetAccountSettings($bIsBase = false)
	{
		$aResponse = array(
			'MessagePerPage' => 20,
			'CheckField' => true
		);

		$aParams = array();
		if ($bIsBase)
		{
			$aParams['Base'] = true;
		}
		
		$this->_echoActionAndResponse('GetAccountSettings', $aResponse, $aParams);
	}

	/**
	 * @param bool $bIsBase = false
	 * @return void
	 */
	public function DoGetFolders($bIsBase = false)
	{
		$aResponse = false;
		
		$oFolders = $this->_oWebMail->Folders();
		if ($oFolders && 0 < $oFolders->Count())
		{
			$aResponse = $this->_createFoldersResponse($oFolders);
		}
		unset($oFolders);

		$aParams = array();
		if ($bIsBase)
		{
			$aParams['Base'] = true;
		}

		$this->_echoActionAndResponse('GetFolders', $aResponse, $aParams);
	}

	/**
	 * @param CCoreLabsWebMailFolderCollection $oFolders
	 * @return array
	 */
	private function _createFoldersResponse(CCoreLabsWebMailFolderCollection $oFolders)
	{
		$aResponse = array();
		$oListFolders =& $oFolders->GetAsArray();
		foreach ($oListFolders as /* @var $oFolder CCoreLabsWebMailFolder */ $oFolder)
		{
			$aAddFolder = array(
				'Type' => $oFolder->Type,
				'FullName' => $oFolder->FullName,
				'Name' => $oFolder->Name,
				'Count' => $oFolder->Count,
				'Unread' => $oFolder->Unread,
				'NoSelect' => $oFolder->NoSelect
			);

			if ($oFolder->SubFolders && 0 < $oFolder->SubFolders->Count())
			{
				$aAddFolder['SubFolders'] = $this->_createFoldersResponse($oFolder->SubFolders);
			}

			$aResponse[] = $aAddFolder;
			unset($oFolder, $aAddFolder);
		}

		return $aResponse;
	}

	/**
	 * @param bool $bIsBase = false
	 * @return void
	 */
	public function DoGetList($bIsBase = false)
	{
		$aResponse = false;
		$aParams = array();

		$iTimeOffsetInSec = (int) $this->_oInputData->GetPostValue('timeoffset', 0);
		$iTimeOffsetInSec *= -60;
		
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', 'INBOX');
		$sSearchDesc = $this->_oInputData->GetPostValue('search', '');
		$iPage = (int) $this->_oInputData->GetPostValue('page', 1);
		$iPage = (0 < $iPage) ? $iPage : 1;
		$iMessagePerPage = $this->_oInputData->GetPostValue('mpp', null);
		$iMessagePerPage = (0 < $iMessagePerPage && 100 > $iMessagePerPage)	? $iMessagePerPage : 20;
		
		$oMessageHeadersList = $this->_oWebMail->MessageHeadersList($sFolderFullName, $iMessagePerPage, $iPage, $sSearchDesc);
		if ($oMessageHeadersList)
		{
			$aResponse = array();
			$oMessageHeaders =& $oMessageHeadersList->GetAsArray();
			foreach ($oMessageHeaders as /* @var $oMessageHeader CCoreLabsWebMailHeader */ $oMessageHeader)
			{
				$sFrom = $sFromShort = '';
				if (null !== $oMessageHeader->From)
				{
					$sFrom = $oMessageHeader->From->ToString();
					$sFromShort = $oMessageHeader->From->DisplayName();
					if (empty($sFromShort))
					{
						$sFromShort = $sFrom;
					}
				}

				$aResponse[] = array(
					'Uid' => $oMessageHeader->Uid,
					'Folder' => $sFolderFullName,
					'Subject' => $oMessageHeader->Subject,
					'Size' => $oMessageHeader->Size,
					'Date' => (null === $oMessageHeader->Date) ? '' : $oMessageHeader->Date->SetTimeOffset($iTimeOffsetInSec)->ToShortFormatString(),
					'From' => $sFrom,
					'FromShort' => $sFromShort,
					'To' => (null === $oMessageHeader->To) ? '' : $oMessageHeader->To->ToString(),
					'Cc' => (null === $oMessageHeader->Cc) ? '' : $oMessageHeader->Cc->ToString(),
					'Bcc' => (null === $oMessageHeader->Bcc) ? '' : $oMessageHeader->Bcc->ToString(),
					'HasAttachments' => $oMessageHeader->HasAttachments,
					'IsUnread' => $oMessageHeader->IsUnread,
					'IsFlagged' => $oMessageHeader->IsFlagged,
					'IsAnswered' => $oMessageHeader->IsAnswered
				);
				unset($oMessageHeader);
			}
			unset($oMessageHeaders);

			$aParams = array(
				'TabName' => CCoreBaseUtils::Utf7ModifiedToUtf8($oMessageHeadersList->Folder).' ~ '.$oMessageHeadersList->Page,
				'Hash' => $oMessageHeadersList->Hash,
				'Page' => $oMessageHeadersList->Page,
				'Folder' => $oMessageHeadersList->Folder,
				'Search' => $oMessageHeadersList->Search,
				'Count' => $oMessageHeadersList->Count,
			);
		}
		unset($oMessageHeadersList);

		if ($bIsBase)
		{
			$aParams['Base'] = true;
		}

		$this->_echoActionAndResponse('GetList', $aResponse, $aParams);
	}

	public function DoGetListHash()
	{
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', 'INBOX');
		$aResponse = $this->_oWebMail->FolderHash($sFolderFullName);

		$aParams = array('Folder' => $sFolderFullName);
		$this->_echoActionAndResponse('GetListHash', $aResponse, $aParams);
	}

	/**
	 * @param bool $bIsBase = false
	 * @return void
	 *
	 * @throw CCoreWebMailException
	 */
	public function DoGetMessagesHeaders($bIsBase = false)
	{
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', '');
		$aUids = $this->_oInputData->GetPostValue('uids', array());
		$sSearchDesc = $this->_oInputData->GetPostValue('search', '');
		$iTimeOffsetInSec = (int) $this->_oInputData->GetPostValue('timeoffset', 0);
		$iTimeOffsetInSec *= -60;

		$aResultHashUids = false;
		$bCalcUids = false;
		if (0 === count($aUids))
		{
			$aResultHashUids = $this->_oWebMail->FolderHashUids('', $sFolderFullName, $sSearchDesc);
			$aUids = array_slice($aResultHashUids['Uids'], 0, 5);
			$bCalcUids = true;
		}

		if (empty($sFolderFullName) || !is_array($aUids) || (!$bCalcUids && 0 === count($aUids)))
		{
			if ($bIsBase)
			{
				$this->_echoActionAndResponse('GetMessagesHeaders', false);
				return false;
			}
			else
			{
				throw new CCoreWebMailException('Internal error');
			}
		}

		$aResponse = false;
		$oMessageHeadersList = $this->_oWebMail->MessageHeaders($sFolderFullName, $aUids);
		if ($oMessageHeadersList)
		{
			$aResponse = array();
			$oMessageHeaders =& $oMessageHeadersList->GetAsArray();
			foreach ($oMessageHeaders as /* @var $oMessageHeader CCoreLabsWebMailHeader */ $oMessageHeader)
			{
				$sFrom = $sFromShort = '';
				if (null !== $oMessageHeader->From)
				{
					$sFrom = $oMessageHeader->From->ToString();
					$sFromShort = $oMessageHeader->From->DisplayName();
					if (empty($sFromShort))
					{
						$sFromShort = $sFrom;
					}
				}

				$aResponse[] = array(
					'Uid' => $oMessageHeader->Uid,
					'Folder' => $sFolderFullName,
					'Subject' => $oMessageHeader->Subject,
					'Size' => $oMessageHeader->Size,
					'Date' => (null === $oMessageHeader->Date) ? '' : $oMessageHeader->Date->SetTimeOffset($iTimeOffsetInSec)->ToShortFormatString(),
					'From' => $sFrom,
					'FromShort' => $sFromShort,
					'To' => (null === $oMessageHeader->To) ? '' : $oMessageHeader->To->ToString(),
					'Cc' => (null === $oMessageHeader->Cc) ? '' : $oMessageHeader->Cc->ToString(),
					'Bcc' => (null === $oMessageHeader->Bcc) ? '' : $oMessageHeader->Bcc->ToString(),
					'HasAttachments' => $oMessageHeader->HasAttachments,
					'IsUnread' => $oMessageHeader->IsUnread,
					'IsFlagged' => $oMessageHeader->IsFlagged,
					'IsAnswered' => $oMessageHeader->IsAnswered
				);
				unset($oMessageHeader);
			}
			unset($oMessageHeaders);
		}
		unset($oMessageHeadersList);
		

		$aParams = array(
			'Folder' => $sFolderFullName,
			'Search' => $sSearchDesc,
			'TabName' => $sFolderFullName.' ('.$sSearchDesc.')',
			'Base' => $bIsBase
		);

		if ($bCalcUids && is_array($aResultHashUids));
		{
			$aParams['CalcUids'] = true;
			$aParams['HashUids'] = $aResultHashUids;
		}

		$this->_echoActionAndResponse('GetMessagesHeaders', $aResponse, $aParams);
		return true;
	}
		
	public function DoGetFolderHashUids()
	{
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', 'INBOX');
		$sSearchDesc = $this->_oInputData->GetPostValue('search', '');
		$sInputHash = $this->_oInputData->GetPostValue('hash', '');

		$aResponse = false;
		$aResult = $this->_oWebMail->FolderHashUids($sInputHash, $sFolderFullName, $sSearchDesc);
		if (is_array($aResult['Uids']))
		{
			$aResponse = $aResult['Uids'];
		}
		else if (true === $aResponse)
		{
			$aResponse = true;
		}

		$aParams = array('Folder' => $sFolderFullName, 'Search' => $sSearchDesc, 'Hash' => $aResult['Hash']);
		$this->_echoActionAndResponse('GetFolderHashUids', $aResponse, $aParams);
	}

	public function DoGetMessages($bIsBase = false)
	{
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', '');
		$aUids = $this->_oInputData->GetPostValue('uids', array());

		if (empty($sFolderFullName) || !is_array($aUids) || 0 === count($aUids))
		{
			if ($bIsBase)
			{
				$this->_echoActionAndResponse('GetMessages', false);
				return false;

			}
			else
			{
				throw new CCoreWebMailException('Internal error');
			}
		}

		$sBaseText = ($bIsBase) ? ' "Base": true,' : '';

		echo '{ "Action": "GetMessages",'.$sBaseText.' "Folder": '.CCoreBaseUtils::Php2js($sFolderFullName).', "Response": [ { ';
		$this->_oWebMail->MessagesThroughCallback($sFolderFullName, $aUids, array($this, 'getMessagesCallback'));
		echo ' } ] }';
	}

	/**
	 * @staticvar string $sLastUid
	 * @staticvar int $iTimeOffsetInSec
	 * @staticvar int $aAtachments
	 * 
	 * @param CCoreLabsWebMailMessage $oMessage
	 * @param string $sType
	 * @param resource $rStream
	 */
	public function getMessagesCallback(CCoreLabsWebMailMessage $oMessage, $sType, $rStream)
	{
		static $aAtachments = array();
		static $sLastUid = null;
		static $iTimeOffsetInSec = null;
		if (null === $iTimeOffsetInSec)
		{
			$iTimeOffsetInSec = (int) $this->_oInputData->GetPostValue('timeoffset', 0);
			$iTimeOffsetInSec *= -60;
		}

		if ($sLastUid !== $oMessage->Uid)
		{
			if (null !== $sLastUid)
			{
				echo ' }, { ';
			}

			$sLastUid = $oMessage->Uid;

			$sFrom = $sFromShort = '';
			if (null !== $oMessage->From)
			{
				$sFrom = $oMessage->From->ToString();
				$sFromShort = $oMessage->From->DisplayName();
				if (empty($sFromShort))
				{
					$sFromShort = $sFrom;
				}
			}

			$aResponse = array(
				'Subject' => $oMessage->Subject,
				'Size' => $oMessage->Size,
				'Date' => (null === $oMessage->Date) ? '' : $oMessage->Date->SetTimeOffset($iTimeOffsetInSec)->ToFormatString('r'),
				'From' => $sFrom,
				'FromShort' => $sFromShort,
				'To' => (null === $oMessage->To) ? '' : $oMessage->To->ToString(),
				'Cc' => (null === $oMessage->Cc) ? '' : $oMessage->Cc->ToString(),
				'Bcc' => (null === $oMessage->Bcc) ? '' : $oMessage->Bcc->ToString(),
				'Charset' => $oMessage->Charset,
				'TabName' => $oMessage->Subject,
				'SaveLink' => 'mail.php?action=save'.
					'&folder='.urlencode($oMessage->Folder).
					'&uid='.urlencode($oMessage->Uid)
			);

			if (is_array($oMessage->Attachments) && 0 < count($oMessage->Attachments))
			{
				$aResponse['Attachments'] = array();
				foreach ($oMessage->Attachments as /* @var $oAttachment CCoreLabsWebMailAttachment */ $oAttachment)
				{
					$aResponse['Attachments'][] = array(
						'Name' => $oAttachment->FileName,
						'Size' => $oAttachment->Size,
						'MimeType' => $oAttachment->MimeType,
						'MimeIndex' => $oAttachment->MimeIndex,
						'Cid' => $oAttachment->Cid,
						'SaveLink' => 'mail.php?action=attachment'.
							'&folder='.urlencode($oMessage->Folder).
							'&uid='.urlencode($oMessage->Uid).
							'&index='.urlencode($oAttachment->MimeIndex)
					);
				}
			}
			
			$aAtachments = isset($aResponse['Attachments']) ? $aResponse['Attachments'] : array();

			echo '"Uid": '.CCoreBaseUtils::Php2js($oMessage->Uid).
				', "Folder": '.CCoreBaseUtils::Php2js($oMessage->Folder).
				', "Info": '.CCoreBaseUtils::Php2js($aResponse);
		}

		if (('html' === $sType))
		{
			echo ', "Html": "';
			$bHasImages = false;
			echo CCoreBaseUtils::InlineRebuildStringToJsString(PrepearHtmlString(
				stream_get_contents($rStream), $aAtachments, $bHasImages));
			echo ($bHasImages) ? '", "HasImages": true' : '"';
		}
		else
		{
			echo ', "Plain": "';
			echo CCoreBaseUtils::InlineRebuildStringToJsString(PrepearPlainString(
				stream_get_contents($rStream)));
			echo '"';
		}
	}

	public function DoSetListFlags()
	{
		$aResponse = false;
		
		$sFolderFullName = $this->_oInputData->GetPostValue('folder', '');
		$aUids = $this->_oInputData->GetPostValue('uids', array());
		$sSetAction = $this->_oInputData->GetPostValue('setaction', '');

		if (!empty($sFolderFullName) && !empty($sSetAction) && is_array($aUids) && 0 < count($aUids))
		{
			switch ($sSetAction)
			{
				case 'AddSeen':
					$aResponse = $this->_oWebMail->SetSeen($sFolderFullName, $aUids, true);
					break;
				case 'RemoveSeen':
					$aResponse = $this->_oWebMail->SetSeen($sFolderFullName, $aUids, false);
					break;
				case 'AddFlag':
					$aResponse = $this->_oWebMail->SetFlagged($sFolderFullName, $aUids, true);
					break;
				case 'RemoveFlag':
					$aResponse = $this->_oWebMail->SetFlagged($sFolderFullName, $aUids, false);
					break;
			}
		}

		$this->_echoActionAndResponse('SetListFlags', $aResponse);
	}
	
	/**
	 * @param string $sAction
	 * @param mixed $mResponse
	 * @param array $aAddParams
	 * @return void
	 */
	private function _echoActionAndResponse($sAction, $mResponse, $aAddParams = array())
	{
		$sAddString = '';
		if (is_array($aAddParams))
		{
			foreach ($aAddParams as $sKey => $sValue)
			{
				$sAddString .= CCoreBaseUtils::Php2js($sKey).': '.CCoreBaseUtils::Php2js($sValue).', ';
			}
		}
		echo '{ "Action": '.CCoreBaseUtils::Php2js($sAction).', '.$sAddString.'"Response": '.CCoreBaseUtils::Php2js($mResponse).' }';
	}
}

/**
 * @param string $sText
 * @return string
 */
function PrepearHtmlString($sText, $aAttachments, &$bHasImages)
{
	$oDom = new DOMDocument('1.0', 'UTF-8');
	// http://php.net/manual/en/domdocument.loadhtml.php#95251
	@$oDom->loadHTML('<?xml version="1.0" encoding="UTF-8"?>'.$sText);
	unset($sText);

	// http://php.net/manual/en/domdocument.loadhtml.php#95251
	foreach ($oDom->childNodes as $oItem)
	{
		if ($oItem->nodeType == XML_PI_NODE)
		{
			$oDom->removeChild($oItem);
		}
	}
	$oDom->encoding = 'UTF-8';

	$aNodes = $oDom->getElementsByTagName('*');
	foreach($aNodes as /* @var $oElement DOMElement */ $oElement)
	{
		$sTagNameLower = strtolower($oElement->tagName);
		if ('a' === $sTagNameLower || 'form' === $sTagNameLower)
		{
			$oElement->setAttribute('target', '_blank');
		}
		
		if ($oElement->hasAttribute('src'))
		{
			$sSrc = trim($oElement->getAttribute('src'));
			if ('cid:' === strtolower(substr($sSrc, 0, 4)))
			{
				foreach ($aAttachments as $aAttachmentItem)
				{
					if (substr($sSrc, 4) === $aAttachmentItem['Cid'])
					{
						$sSrc = $aAttachmentItem['SaveLink'];
						$oElement->setAttribute('src', $sSrc);
						break;
					}
				}
			}

			$oElement->removeAttribute('src');
			$oElement->setAttribute('data-x-src', $sSrc);
			$bHasImages = true;
		}
		
		if ($oElement->hasAttribute('style'))
		{
			$aOutStyles = array();
			$sStyle = trim($oElement->getAttribute('style'));
			$aStyles = explode(';', $sStyle);

			foreach ($aStyles as $iKey => $sStyleItem)
			{
				$aStyleValue = explode(':', $sStyleItem, 2);
				$sName = trim(strtolower($aStyleValue[0]));
				if (in_array($sName, array('position', 'left', 'right', 'top', 'bottom')))
				{
					continue;
				}
				else if ('height' === $sName)
				{
					$aOutStyles[] = 'min-'.ltrim($sStyleItem);
				}
				else
				{
					$aOutStyles[] = $sStyleItem;
				}
			}

			$oElement->setAttribute('style', implode(';', $aOutStyles));
		}
	}

	$sText = $oDom->saveHTML();
	unset($oDom);

	$aToRemove = array(
		"'<!doctype[^>]*>'si",
		"'<html[^>]*>'si",
		"'</html>'si",
		"'<head[^>]*>.*?</head>'si",
		"'<link[^>]*>'si",
		"'<base[^>]*>'si",
		"'<title[^>]*>.*?</title>'si",
		"'<style[^>]*>.*?</style>'si",
		"'<script[^>]*>.*?</script>'si",
		"'</script>'si",
		"'<object[^>]*>.*?</object>'si",
		"'<embed[^>]*>.*?</embed>'si",
		"'<applet[^>]*>.*?</applet>'si",
		"'<mocha[^>]*>.*?</mocha>'si"
	);

	$sText = preg_replace($aToRemove, '', $sText);
	$sText = preg_replace('/<([\/]{0,1})body([^>]*?)>/im', '<\\1div\\2>', $sText);
	$sText = preg_replace('/[\r\n\t]/m', ' ', $sText);
	return trim($sText);
}

/**
 * @param string $sText
 * @return string
 */
function PrepearPlainString($sText)
{
	$sText = str_replace("\r", '', $sText);

	$aReplaceStrings = array();
	$sPattern = '/(http|https|ftp|news|svn):\/\/([a-z0-9+-=%&@:_\.~\?\*]+[#a-z0-9+]*[^\.\,<>()\s\]"\'])/ie';
	$sReplace = 'PlainStrReplacement(\'<a href="\\1://\\2" target="_blank">\\1://\\2</a>\', $aReplaceStrings)';
	$sText = preg_replace($sPattern, $sReplace, $sText);

	$sPattern = '/(www\.[a-z0-9+-=%&@:_\.~\?\*]+[#a-z0-9+]*[^\.\,<>()\s\]"\'])/ie';
	$sReplace = 'PlainStrReplacement(\'<a href="http://\\1" target="_blank">\\1</a>\', $aReplaceStrings)';
	$sText = preg_replace($sPattern, $sReplace, $sText);

	$sPattern = '/ ([a-z0-9-\.]+\.(com|net|org|ru)[a-z0-9+-=%&@:_\.~\?\*]*[#a-z0-9+]*[^\.\,<>()\s\]"\'])/ie';
	$sReplace = 'PlainStrReplacement(\' <a href="http://\\1" target="_blank">\\1</a>\', $aReplaceStrings)';
	$sText = preg_replace($sPattern, $sReplace, $sText);

	$sText = htmlspecialchars($sText);

	$sText = preg_replace('/([0-9a-zA-Z_+\.\-]+@[0-9a-zA-Z_+\.\-]+\.[a-zA-Z]{2,})/', '<a href="mailto:\\1" target="_blank">\\1</a>', $sText);

	for ($i = 0, $c = count($aReplaceStrings); $i < $c; $i++)
	{
		if (isset($aReplaceStrings[$i]))
		{
			$sText = str_replace('@@link{'.$i.'}link@@', $aReplaceStrings[$i], $sText);
		}
	}
	unset($aReplaceStrings);

	$aText = explode("\n", $sText);
	foreach ($aText as &$sTextLine)
	{
		if (preg_match('/^[^&;]{0,20}&gt;/', $sTextLine))
		{
			$sTextLine = '<font class="quotation">'.$sTextLine.'</font>';
		}
	}
	$sText = join("<br />\n", $aText);
	unset($aText);	

	$sText = str_replace("\t", '&nbsp;&nbsp;&nbsp;', $sText);
	$sText = str_replace('  ', '&nbsp;&nbsp;', $sText);
	
	return $sText;
}

function PlainStrReplacement($sText, &$aReplace)
{
	$aReplace[] = stripslashes($sText);
	return '@@link{'.(count($aReplace) - 1).'}link@@';
}